//===================================================================================
// Microsoft patterns & practices
// Composite Application Guidance for Windows Presentation Foundation and Silverlight
//===================================================================================
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===================================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===================================================================================
using System;
using System.Collections.Generic;

namespace Microsoft.Practices.Prism
{
	/// <summary>
	/// Class that provides extension methods for the Exception class. These extension methods provide
	/// a mechanism for developers to get more easily to the root cause of an exception, especially in combination with 
	/// DI-containers such as Unity. 
	/// </summary>
	public static class ExceptionExtensions
	{
		private static List<Type> frameworkExceptionTypes = new List<Type>();

		/// <summary>
		/// Register the type of an Exception that is thrown by the framework. The <see cref="GetRootException"/> method uses
		/// this list of Exception types to find out if something has gone wrong.  
		/// </summary>
		/// <param name="frameworkExceptionType">The type of exception to register.</param>
		public static void RegisterFrameworkExceptionType(Type frameworkExceptionType)
		{
			if (frameworkExceptionType == null) throw new ArgumentNullException("frameworkExceptionType");

			if (!frameworkExceptionTypes.Contains(frameworkExceptionType))
				frameworkExceptionTypes.Add(frameworkExceptionType);
		}


		/// <summary>
		/// Determines whether the exception type is already registered using the <see cref="RegisterFrameworkExceptionType"/> 
		/// method
		/// </summary>
		/// <param name="frameworkExceptionType">The type of framework exception to find.</param>
		/// <returns>
		/// 	<c>true</c> if the exception type is already registered; otherwise, <c>false</c>.
		/// </returns>
		public static bool IsFrameworkExceptionRegistered(Type frameworkExceptionType)
		{
			return frameworkExceptionTypes.Contains(frameworkExceptionType);
		}

		/// <summary>
		/// Looks at all the inner exceptions of the <paramref name="exception"/> parameter to find the 
		/// most likely root cause of the exception. This works by skipping all registered exception types.
		/// </summary>
		/// <remarks>
		/// This method is not 100% accurate and should only be used to point a developer into the most likely direction.
		/// It should not be used to replace the Inner Exception stack of an exception, because this might hide required exception
		/// information. 
		/// </remarks>
		/// <param name="exception">The exception that will provide the list of inner exeptions to examine.</param>
		/// <returns>
		/// The exception that most likely caused the exception to occur. If it can't find the root exception, it will return the 
		/// <paramref name="exception"/> value itself.
		/// </returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We have to catch exception. This method is used in exception handling code, so it must not fail.")]
		public static Exception GetRootException(this Exception exception)
		{
			Exception rootException = exception;

			try
			{
				while (true)
				{
					if (rootException == null)
					{
						rootException = exception;
						break;
					}

					if (!IsFrameworkException(rootException))
					{
						break;
					}
					rootException = rootException.InnerException;
				}
			}
			catch (Exception)
			{
				rootException = exception;
			}
			return rootException;
		}

		private static bool IsFrameworkException(Exception exception)
		{
			bool isFrameworkException = frameworkExceptionTypes.Contains(exception.GetType());
			bool childIsFrameworkException = false;

			if (exception.InnerException != null)
			{
				childIsFrameworkException = frameworkExceptionTypes.Contains(exception.InnerException.GetType());
			}

			return isFrameworkException || childIsFrameworkException;
		}
	}
}